namespace Bowlus.Common.Communications
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.ServiceModel;
    using System.ServiceModel.Channels;

    using Bowlus.Common;

    using Microsoft.WindowsAzure.ServiceRuntime;

    public sealed class CommunicationsClient : IDisposable
    {
        // Bowlus.FrontEnd, Bowlus.AppHost
        // Use role name as endpoint name
        private readonly string clientRoleName;

        // Bowlus.FrontEnd, Bowlus.AppHost
        // Use role name as endpoint name
        private readonly string serverRoleName;

        private static readonly NetTcpBinding Binding = new NetTcpBinding(SecurityMode.None);

        private readonly ChannelFactory<ICommunicationService> factory = new ChannelFactory<ICommunicationService>(Binding);

        public CommunicationsClient()
        {
            this.clientRoleName = RoleEnvironment.CurrentRoleInstance.Role.Name;
            this.serverRoleName = this.clientRoleName == Settings.FrontEndRoleName ? Settings.AppHostRoleName : Settings.FrontEndRoleName;
        }

        // notify all instances in another role
        public void SendMessageToOtherRole(BowlusMessage message)
        {
            var messageTypeStr = Enum.GetName(typeof(BowlusMessageType), message.MessageType);

            var endPoints = RoleEnvironment.Roles[this.serverRoleName].Instances.Select(instance => instance.InstanceEndpoints[this.serverRoleName]);

            foreach (var ep in endPoints)
            {
                var address = new EndpointAddress(
                    string.Format("net.tcp://{0}/{1}", ep.IPEndpoint, this.serverRoleName));
                var client = this.factory.CreateChannel(address);
                
                try
                {
                    if (client != null)
                    {
                        Trace.TraceInformation(string.Format("*** HandleMessage {0}/{1}: {2}", ep.IPEndpoint, this.serverRoleName, messageTypeStr));

                        client.HandleMessage(message);
                        ((IChannel)client).Close();
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message);
                    var communicationObject = (IChannel)client;
                    if (communicationObject != null)
                    {
                        communicationObject.Abort();
                    }
                }
            }
        }

        public void SendMessageToRoleInstance(BowlusMessage message, IPEndPoint roleInstanceEndpoint, string endPointName)
        {
            var messageTypeStr = Enum.GetName(typeof(BowlusMessageType), message.MessageType);

            var address = new EndpointAddress(
                string.Format("net.tcp://{0}/{1}", roleInstanceEndpoint, endPointName));
            var client = this.factory.CreateChannel(address);

            try
            {
                if (client != null)
                {
                    Trace.TraceInformation(string.Format("*** Send message {0} from: {1} to: {2}/{3}", messageTypeStr, this.serverRoleName, roleInstanceEndpoint, endPointName));

                    client.HandleMessage(message);
                    ((IChannel)client).Close();
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message);
                var communicationObject = (IChannel)client;
                if (communicationObject != null)
                {
                    communicationObject.Abort();
                }
            }
        }

        // notify all other instances in this same role
        public void SendMessageToOtherInstances(BowlusMessage message)
        {
            var messageTypeStr = Enum.GetName(typeof(BowlusMessage), message);

            var endPoints = RoleEnvironment.Roles[this.clientRoleName].Instances.Select(instance => instance.InstanceEndpoints[this.clientRoleName]);
            var myEndPoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints[this.clientRoleName];

            foreach (var ep in endPoints)
            {
                if (ep.IPEndpoint.ToString().Equals(myEndPoint.IPEndpoint.ToString()))
                {
                    continue;
                }

                var address = new EndpointAddress(
                    string.Format("net.tcp://{0}/{1}", ep.IPEndpoint, this.clientRoleName));
                var client = this.factory.CreateChannel(address);

                try
                {
                    if (client != null)
                    {
                        Trace.TraceInformation(string.Format("*** HandleMessage {0}/{1}: {2}", ep.IPEndpoint, this.clientRoleName, messageTypeStr));

                        client.HandleMessage(message);
                        ((IChannel)client).Close();
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message);
                    var communicationObject = (IChannel)client;
                    if (communicationObject != null)
                    {
                        communicationObject.Abort();
                    }
                }
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!disposing)
            {
                return;
            }

            if (null != this.factory)
            {
                this.factory.Close();
            }
        }
    }
}